/**
 * 
 * Copyright (C) 2011 Cody Stoutenburg . All rights reserved.
 *
 *       This program is free software; you can redistribute it and/or
 *       modify it under the terms of the GNU Lesser General Public License
 *       as published by the Free Software Foundation; either version 2.1
 *       of the License, or (at your option) any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *       GNU Lesser General Public License for more details.
 *
 *       You should have received a copy of the GNU Lesser General Public License
 *       along with this program; if not, write to the Free Software
 *       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 * 
 */
package ca.usherbrooke.agent;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.newdawn.slick.geom.Rectangle;

import ca.usherbrooke.agent.controller.BasicAgentLauncher;
import ca.usherbrooke.agent.controller.shared.IAgent;
import ca.usherbrooke.application.astar.AStar;
import ca.usherbrooke.application.astar.AStarWorld;
import ca.usherbrooke.application.astar.Road;
import ca.usherbrooke.application.astar.action.AStarMoveAction;
import ca.usherbrooke.application.astar.action.WalkAction;
import ca.usherbrooke.application.astar.action.builder.OnlyWalkActionBuilder;
import ca.usherbrooke.application.astar.goal.GoalMove;
import ca.usherbrooke.application.astar.heuristique.HeuristiqueMove;
import ca.usherbrooke.application.astar.heuristique.HeuristiqueNull;
import ca.usherbrooke.application.astar.location.BasicLocation;
import ca.usherbrooke.application.astar.location.CompleteLocation;
import ca.usherbrooke.application.astar.location.ILocation;
import ca.usherbrooke.behaviour.MoveBehaviour;
import ca.usherbrooke.model.entity.IEntityModel;
import ca.usherbrooke.model.feature.GroundModel;
import ca.usherbrooke.model.feature.WallModel;
import ca.usherbrooke.model.world.IWorldModel;
import ca.usherbrooke.tools.position.Vector2D;
import ca.usherbrooke.view.slick.jade.worldView.shared.EntityViewDebug;
import ca.usherbrooke.world.representation.SimpleWorld;

/**
 * @author Cody Stoutenburg
 * 
 */
public abstract class SimpleMoveAgentController extends IAgent {
	private static final long serialVersionUID = 1L;

	public static Float X_LOCATION_DISTANCE = 8.0f;
	public static Float Y_LOCATION_DISTANCE = 8.0f;

	private static OnlyWalkActionBuilder WALK_ACTION_BUILDER = new OnlyWalkActionBuilder();
	private static HeuristiqueNull HEURISTIQUE_NULL = new HeuristiqueNull();
	private static HeuristiqueMove HEURISTIQUE_MOVE = new HeuristiqueMove();

	// private SimpleWorld simpleWorld;
	private SimpleWorld simpleWorld;

	// the represent the real world,
	// it take in account:
	// - the ground speed modifier
	// - the walls
	// - the current position of other entity
	private List<CompleteLocation> allRealLocation;

	// this represent the world only with wall and distance, does not matter the
	// speed factor or if some entity block the way
	private List<BasicLocation> allBasicLocations;

	private MoveBehaviour move;

	/**
	 * @param agent
	 */
	public SimpleMoveAgentController(BasicAgentLauncher agent) {
		super(agent);
	}

	@Override
	public void initEntity() {
		super.initEntity();

		simpleWorld = new SimpleWorld(getWorld());

		// <CompleteLocation>
		allRealLocation = generateAllLinkedLocation(simpleWorld,
				generateCompleteLocation(simpleWorld, getEntity()));
		allBasicLocations = generateAllLinkedLocation(simpleWorld,
				generateBasicLocation(simpleWorld, getEntity()));

		// print debug info
		generateLocationPrintDebug();

		move = new MoveBehaviour(this);
		this.addBehaviour(move);
	}

	@Override
	public void worldChanged(IWorldModel world) {
		super.worldChanged(world);

		simpleWorld.mergeWorld(getWorld());

		// <CompleteLocation>
		allRealLocation = generateAllLinkedLocation(simpleWorld,
				generateCompleteLocation(simpleWorld, getEntity()));
		allBasicLocations = generateAllLinkedLocation(simpleWorld,
				generateBasicLocation(simpleWorld, getEntity()));

		// print debug info
		generateLocationPrintDebug();
	}

	public IWorldModel getKnownWorld() {
		return simpleWorld;
	}

	public void setPlan(List<AStarMoveAction> allActions) {
		move.replaceCurrentPlan(allActions);
	}

	public List<CompleteLocation> getCompleteLocation() {
		return this.allRealLocation;
	}

	public List<BasicLocation> getBasicLocation() {
		return this.allBasicLocations;
	}

	// PLAN BUILDER
	public BasicLocation getClosestBasicLocation(Vector2D destination) {
		return getClosestEmplacement(getBasicLocation(), destination);
	}

	public CompleteLocation getClosestCompleteLocation(Vector2D destination) {
		return getClosestEmplacement(getCompleteLocation(), destination);
	}

	public List<AStarMoveAction> generateSimpleFastPlan(
			BasicLocation destination) {
		return generateSimpleFastPlan(destination, AStar.INFINI_TIME);
	}

	public List<AStarMoveAction> generateSimpleFastPlan(
			BasicLocation destination, long timeInMs) {
		List<BasicLocation> locations = getBasicLocation();

		Vector2D center = new Vector2D(this.getEntity().getSize().getCenterX(),
				this.getEntity().getSize().getCenterY());
		BasicLocation startLocation = getClosestEmplacement(locations, center);

		// setup
		AStar<GoalMove> aStar = new AStar<GoalMove>();
		AStarWorld world = new AStarWorld(this, WALK_ACTION_BUILDER);

		// run
		List<AStarMoveAction> plan = aStar.generatePlan(world, startLocation,
				HEURISTIQUE_MOVE, new GoalMove(destination), timeInMs);

		return plan;
	}

	public List<AStarMoveAction> generateRealPlan(CompleteLocation destination) {
		return generateRealPlan(destination, AStar.INFINI_TIME);
	}

	public List<AStarMoveAction> generateRealPlan(CompleteLocation destination,
			long timeInMs) {
		List<CompleteLocation> allLocation = this.getCompleteLocation();

		for (CompleteLocation location : allLocation) {
			location.revalidate(this.getWorld());
		}

		Vector2D center = new Vector2D(this.getEntity().getSize().getCenterX(),
				this.getEntity().getSize().getCenterY());
		CompleteLocation startLocation = getClosestEmplacement(allLocation,
				center);
		// setup
		AStar<GoalMove> aStar = new AStar<GoalMove>();
		AStarWorld world = new AStarWorld(this, WALK_ACTION_BUILDER);

		// run
		List<AStarMoveAction> plan = aStar.generatePlan(world, startLocation,
				HEURISTIQUE_NULL, new GoalMove(destination), timeInMs);
		return plan;
	}

	public void clearCurrentPlan() {
		this.move.replaceCurrentPlan(Collections.<AStarMoveAction> emptyList());
	}

	public static List<AStarMoveAction> cleanPlan(List<AStarMoveAction> plan) {
		Vector2D previousDirection = null;
		Vector2D currentDirection;

		List<AStarMoveAction> newPlan = new ArrayList<AStarMoveAction>();
		ILocation begin = null;

		Iterator<AStarMoveAction> itt = plan.iterator();

		while (itt.hasNext()) {
			AStarMoveAction action = itt.next();
			if (action instanceof WalkAction) {
				WalkAction move = (WalkAction) action;
				currentDirection = move.getEnd().getPosition()
						.sub(move.getBegin().getPosition());
				if (previousDirection == null) {
					previousDirection = currentDirection;
					begin = move.getBegin();
				}
				if (!currentDirection.getAt().equals(previousDirection.getAt())) {
					newPlan.add(new WalkAction(begin, move.getBegin()));
					newPlan.add(move);
					begin = move.getEnd();
				}
				previousDirection = currentDirection;

				if (!itt.hasNext()) {
					newPlan.add(new WalkAction(begin, move.getBegin()));
					newPlan.add(move);
					begin = move.getEnd();
				}
			} else {
				newPlan.add(action);
			}
		}

		return newPlan;
	}

	public ILocation getClosestEmplacementBasicLocations(Vector2D position) {
		return getClosestEmplacement(this.allBasicLocations, position);
	}

	private static <E extends ILocation> E getClosestEmplacement(
			List<E> emplacements, Vector2D position) {
		E result = null;
		double distanceMin = Double.MAX_VALUE;

		for (E location : emplacements) {
			double distance = location.getPosition().getSquaredDistance(
					position);
			if (distance < distanceMin) {
				distanceMin = distance;
				result = location;
			}
		}

		return result;
	}

	private void generateLocationPrintDebug() {
		List<Vector2D> allPosition = new ArrayList<Vector2D>();
		List<Vector2D> allWalls = new ArrayList<Vector2D>();

		for (CompleteLocation location : this.getCompleteLocation()) {
			if (location.isWall()) {
				allWalls.add(location.getPosition());
			} else {
				allPosition.add(location.getPosition());
			}
		}

		// verify if new info in positions
		List<Vector2D> allPos = EntityViewDebug.INSTANCE
				.getAllPosition(getEntity().getGuid());
		Boolean containAllPos = true;
		for (Vector2D pos : allPosition) {
			if (!allPos.contains(pos)) {
				containAllPos = false;
			}
		}

		// verify if new info in walls
		List<Vector2D> allWall = EntityViewDebug.INSTANCE
				.getAllWalls(getEntity().getGuid());
		Boolean containAllWalls = true;
		for (Vector2D pos : allWalls) {
			if (!allWall.contains(pos)) {
				containAllWalls = false;
			}
		}

		if (!containAllPos) {
			EntityViewDebug.INSTANCE.clearPosition(getEntity().getGuid());
			EntityViewDebug.INSTANCE.addPosition(getEntity().getGuid(),
					allPosition);
		}

		if (!containAllWalls) {
			EntityViewDebug.INSTANCE.clearWalls(getEntity().getGuid());
			EntityViewDebug.INSTANCE.addWall(getEntity().getGuid(), allWalls);
		}
	}

	public List<ILocation> buildAllVisibleLocation() {
		List<ILocation> allLocation = new ArrayList<ILocation>();

		CompleteLocation[][] allBase = generateCompleteLocation(getWorld(),
				getEntity());

		for (CompleteLocation[] completeLocations : allBase) {
			for (CompleteLocation completeLocation : completeLocations) {
				if (completeLocation != null) {
					CompleteLocation loc = getClosestCompleteLocation(completeLocation
							.getPosition());
					if (loc != null) {
						allLocation.add(completeLocation);
					}
				}
			}
		}

		return allLocation;
	}

	private static Float getGroundSpeedFactor(IWorldModel world,
			Vector2D position) {
		Float speedFactor = 0.00001f;
		GroundModel ground = world.getGround(position);
		if (ground != null) {
			speedFactor = ground.getSpeedFactor();
		}
		return speedFactor;
	}

	private static CompleteLocation[][] generateCompleteLocation(
			IWorldModel world, IEntityModel entity) {

		Rectangle size = entity.getSize();
		float entityWidth = size.getWidth();
		float entityHeight = size.getHeight();

		Rectangle worldSize = world.getSize();
		int width = (int) Math
				.floor(worldSize.getWidth() / X_LOCATION_DISTANCE);
		int height = (int) Math.floor(worldSize.getHeight()
				/ Y_LOCATION_DISTANCE);

		CompleteLocation[][] allEmplacement = new CompleteLocation[width][height];

		// generate all emplacement
		for (int i = 0; i < width; ++i) {
			for (int j = 0; j < height; ++j) {
				allEmplacement[i][j] = buildCompleteLocation(world, entity,
						new Vector2D(i * X_LOCATION_DISTANCE, j
								* Y_LOCATION_DISTANCE), entityWidth,
						entityHeight);
			}
		}

		return allEmplacement;
	}

	private static BasicLocation buildBasicLocation(IWorldModel world,
			Vector2D position, float width, float height) {
		BasicLocation newEmp = null;

		Rectangle rect = new Rectangle(0, 0, width, height);

		Boolean notWall = true;
		Vector2D test = position.clone();
		test.addX(-(width / 2));
		test.addY(-(height / 2));
		rect.setLocation(test.getX().floatValue(), test.getY().floatValue());
		List<WallModel> wall = world.getWall(rect);

		notWall = world.getWall(position) == null && wall.isEmpty();

		if (notWall) {
			newEmp = new BasicLocation(position, false);
		}
		return newEmp;
	}

	private static BasicLocation[][] generateBasicLocation(IWorldModel world,
			IEntityModel entity) {

		Rectangle size = entity.getSize();
		float entityWidth = size.getWidth();
		float entityHeight = size.getHeight();

		Rectangle worldSize = world.getSize();
		int width = (int) Math
				.floor(worldSize.getWidth() / X_LOCATION_DISTANCE);
		int height = (int) Math.floor(worldSize.getHeight()
				/ Y_LOCATION_DISTANCE);

		BasicLocation[][] allEmplacement = new BasicLocation[width][height];

		// generate all emplacement
		for (int i = 0; i < width; ++i) {
			for (int j = 0; j < height; ++j) {
				allEmplacement[i][j] = buildBasicLocation(world, new Vector2D(i
						* X_LOCATION_DISTANCE, j * Y_LOCATION_DISTANCE),
						entityWidth, entityHeight);
			}
		}

		return allEmplacement;
	}

	private static <E extends ILocation> List<E> generateAllLinkedLocation(
			final IWorldModel world, final E[][] allEmplacement) {
		List<E> emplacements = new ArrayList<E>();

		Rectangle worldSize = world.getSize();
		int width = (int) Math
				.floor(worldSize.getWidth() / X_LOCATION_DISTANCE);
		int height = (int) Math.floor(worldSize.getHeight()
				/ Y_LOCATION_DISTANCE);

		// generate all road for emplacement 1 to i-1
		for (int i = 0; i < width - 1; ++i) {
			for (int j = 0; j < height; ++j) {
				if (allEmplacement[i][j] != null) {
					ArrayList<Road> allRoad = new ArrayList<Road>();

					ArrayList<ILocation> neighbor = new ArrayList<ILocation>();
					if (i > 0 && allEmplacement[i - 1][j] != null) {
						neighbor.add(allEmplacement[i - 1][j]);

						if (!allEmplacement[i - 1][j].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i - 1][j]));
						}
					}
					if (i < width - 1 && allEmplacement[i + 1][j] != null) {
						neighbor.add(allEmplacement[i + 1][j]);

						if (!allEmplacement[i + 1][j].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i + 1][j]));
						}
					}
					if (j > 0 && allEmplacement[i][j - 1] != null) {
						neighbor.add(allEmplacement[i][j - 1]);

						if (!allEmplacement[i][j - 1].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i][j - 1]));
						}
					}
					if (j < height - 1 && allEmplacement[i][j + 1] != null) {
						neighbor.add(allEmplacement[i][j + 1]);

						if (!allEmplacement[i][j + 1].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i][j + 1]));
						}
					}

					if (i > 0 && j > 0 && allEmplacement[i - 1][j - 1] != null
							&& allEmplacement[i - 1][j] != null
							&& allEmplacement[i][j - 1] != null) {
						neighbor.add(allEmplacement[i - 1][j - 1]);

						if (!allEmplacement[i - 1][j - 1].isWall()
								&& !allEmplacement[i - 1][j].isWall()
								&& !allEmplacement[i][j - 1].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i - 1][j - 1]));
						}
					}

					if (i > 0 && j < height - 1
							&& allEmplacement[i - 1][j + 1] != null
							&& allEmplacement[i - 1][j] != null
							&& allEmplacement[i][j + 1] != null) {
						neighbor.add(allEmplacement[i - 1][j + 1]);

						if (!allEmplacement[i - 1][j + 1].isWall()
								&& !allEmplacement[i - 1][j].isWall()
								&& !allEmplacement[i][j + 1].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i - 1][j + 1]));
						}
					}

					if (i < width - 1 && j > 0
							&& allEmplacement[i + 1][j - 1] != null
							&& allEmplacement[i + 1][j] != null
							&& allEmplacement[i][j - 1] != null) {
						neighbor.add(allEmplacement[i + 1][j - 1]);

						if (!allEmplacement[i + 1][j - 1].isWall()
								&& !allEmplacement[i + 1][j].isWall()
								&& !allEmplacement[i][j - 1].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i + 1][j - 1]));
						}
					}

					if (i < width - 1 && j < height - 1
							&& allEmplacement[i + 1][j + 1] != null
							&& allEmplacement[i + 1][j] != null
							&& allEmplacement[i][j + 1] != null) {
						neighbor.add(allEmplacement[i + 1][j + 1]);

						if (!allEmplacement[i + 1][j + 1].isWall()
								&& !allEmplacement[i + 1][j].isWall()
								&& !allEmplacement[i][j + 1].isWall()) {
							allRoad.add(new Road(allEmplacement[i][j],
									allEmplacement[i + 1][j + 1]));
						}
					}

					allEmplacement[i][j].setRoads(allRoad);
					allEmplacement[i][j].setNeighbor(neighbor);
				}
			}
		}

		for (int i = 0; i < width; ++i) {
			for (int j = 0; j < height; ++j) {
				if (allEmplacement[i][j] != null) {
					emplacements.add(allEmplacement[i][j]);
				}
			}
		}

		return emplacements;
	}

	private static CompleteLocation buildCompleteLocation(IWorldModel world,
			IEntityModel entity, Vector2D position, float width, float height) {
		CompleteLocation newEmp = null;

		Rectangle rect = new Rectangle(0, 0, width, height);

		Vector2D test = position.clone();
		test.addX(-(width / 2));
		test.addY(-(height / 2));
		rect.setLocation(test.getX().floatValue(), test.getY().floatValue());
		List<WallModel> wall = world.getWall(rect);

		// if (wall.isEmpty()) {
		newEmp = new CompleteLocation(position, world, entity,
				getGroundSpeedFactor(world, position), !wall.isEmpty());
		// }
		return newEmp;
	}
}
